<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>StarRocks to TiDB 同步配置管理</title>
    <!-- Element Plus CSS - 本地资源（内网部署） -->
    <link rel="stylesheet" href="/lib/css/element-plus.css">
    <style>
        body {
            margin: 0;
            padding: 0;
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
        }
        #app {
            padding: 20px;
        }
        .header {
            margin-bottom: 20px;
        }
        .sync-status {
            margin-bottom: 20px;
        }
        .field-mapping-table {
            margin-top: 10px;
        }
        /* 实时日志样式 */
        .log-container {
            height: 600px;
            overflow-y: auto;
            background-color: #1e1e1e;
            padding: 15px;
            border-radius: 4px;
            font-family: 'Courier New', Consolas, monospace;
            font-size: 13px;
            line-height: 1.6;
        }
        .log-line {
            margin-bottom: 2px;
            word-wrap: break-word;
        }
        .log-timestamp {
            color: #808080;
            margin-right: 8px;
        }
        .log-thread {
            color: #c586c0;
            margin-right: 8px;
        }
        .log-level {
            font-weight: bold;
            margin-right: 8px;
            display: inline-block;
            width: 50px;
        }
        .log-logger {
            color: #4ec9b0;
            margin-right: 8px;
        }
        .log-message {
            color: #d4d4d4;
        }
        /* 日志级别颜色 */
        .log-trace .log-level { color: #808080; }
        .log-debug .log-level { color: #569cd6; }
        .log-info .log-level { color: #4ec9b0; }
        .log-warn .log-level { color: #ce9178; }
        .log-error .log-level { color: #f48771; }
        
        /* 滚动条样式 */
        .log-container::-webkit-scrollbar {
            width: 10px;
        }
        .log-container::-webkit-scrollbar-track {
            background: #2d2d2d;
        }
        .log-container::-webkit-scrollbar-thumb {
            background: #555;
            border-radius: 5px;
        }
        .log-container::-webkit-scrollbar-thumb:hover {
            background: #666;
        }
    </style>
</head>
<body>
<div id="app">
    <el-container>
        <el-header class="header">
            <h1>🚀 StarRocks to TiDB 数据同步配置管理</h1>
        </el-header>
        
        <el-main>
            <!-- 同步服务状态 -->
            <el-card class="sync-status" shadow="hover">
                <template #header>
                    <div style="display: flex; justify-content: space-between; align-items: center;">
                        <span>同步服务状态</span>
                        <div>
                            <el-button type="success" size="small" @click="startSync" :loading="syncLoading">启动同步</el-button>
                            <el-button type="danger" size="small" @click="stopSync" :loading="syncLoading">停止同步</el-button>
                            <el-button type="primary" size="small" @click="reloadConfig" :loading="reloadLoading">重新加载配置</el-button>
                            <el-button type="info" size="small" @click="refreshStatus">刷新状态</el-button>
                        </div>
                    </div>
                </template>
                <div v-if="syncStatus">
                    <el-descriptions :column="3" border>
                        <el-descriptions-item label="运行状态">
                            <el-tag :type="syncStatus.running ? 'success' : 'info'">
                                {{ syncStatus.running ? '运行中' : '已停止' }}
                            </el-tag>
                        </el-descriptions-item>
                        <el-descriptions-item label="配置数量">
                            {{ syncStatus.tableCount || 0 }}
                        </el-descriptions-item>
                        <el-descriptions-item label="配置来源">
                            <el-tag type="primary">{{ syncStatus.configSource || '未知' }}</el-tag>
                        </el-descriptions-item>
                    </el-descriptions>
                </div>
            </el-card>
            
            <!-- 主内容区域 - 使用Tab -->
            <el-tabs v-model="activeMainTab" type="card">
                <!-- 配置管理标签页 -->
                <el-tab-pane label="同步配置管理" name="config">
                    <div style="margin-bottom: 15px;">
                        <el-button type="primary" @click="showAddDialog">➕ 新增配置</el-button>
                    </div>
                    
                    <el-table :data="configs" stripe border>
                    <el-table-column prop="id" label="ID" width="80"></el-table-column>
                    <el-table-column prop="sourceTableName" label="源表名" width="200"></el-table-column>
                    <el-table-column prop="primaryKey" label="主键" width="120"></el-table-column>
                    <el-table-column prop="updateTimeField" label="时间字段" width="120"></el-table-column>
                    <el-table-column label="目标表数量" width="120">
                        <template #default="scope">
                            <el-tag type="success">{{ scope.row.targets ? scope.row.targets.length : 0 }}</el-tag>
                        </template>
                    </el-table-column>
                    <el-table-column prop="batchSize" label="批量大小" width="100"></el-table-column>
                    <el-table-column label="全量同步" width="100">
                        <template #default="scope">
                            <el-tag :type="scope.row.fullSyncOnStartup ? 'warning' : 'info'">
                                {{ scope.row.fullSyncOnStartup ? '是' : '否' }}
                            </el-tag>
                        </template>
                    </el-table-column>
                    <el-table-column label="状态" width="80">
                        <template #default="scope">
                            <el-switch v-model="scope.row.enabled" @change="toggleConfig(scope.row)"></el-switch>
                        </template>
                    </el-table-column>
                    <el-table-column prop="description" label="描述" min-width="150"></el-table-column>
                    <el-table-column label="操作" width="180" fixed="right">
                        <template #default="scope">
                            <el-button size="small" type="primary" @click="showEditDialog(scope.row)">编辑</el-button>
                            <el-button size="small" type="danger" @click="deleteConfig(scope.row.id)">删除</el-button>
                        </template>
                    </el-table-column>
                </el-table>
                </el-tab-pane>
                
                <!-- 同步日志标签页 -->
                <el-tab-pane label="同步日志" name="log">
                    <!-- 查询条件 -->
                    <el-form :inline="true" :model="logQuery" style="margin-bottom: 15px;">
                        <el-form-item label="源表名">
                            <el-select v-model="logQuery.sourceTableName" placeholder="请选择源表" clearable filterable style="width: 200px;">
                                <el-option
                                    v-for="table in logSourceTables"
                                    :key="table"
                                    :label="table"
                                    :value="table">
                                </el-option>
                            </el-select>
                        </el-form-item>
                        <el-form-item label="目标表名">
                            <el-select v-model="logQuery.targetTableName" placeholder="请选择目标表" clearable filterable style="width: 200px;">
                                <el-option
                                    v-for="table in logTargetTables"
                                    :key="table"
                                    :label="table"
                                    :value="table">
                                </el-option>
                            </el-select>
                        </el-form-item>
                        <el-form-item label="同步类型">
                            <el-select v-model="logQuery.syncType" placeholder="请选择" clearable style="width: 150px;">
                                <el-option label="全量同步" value="FULL"></el-option>
                                <el-option label="增量同步" value="INCREMENTAL"></el-option>
                            </el-select>
                        </el-form-item>
                        <el-form-item label="同步状态">
                            <el-select v-model="logQuery.syncStatus" placeholder="请选择" clearable style="width: 150px;">
                                <el-option label="成功" value="SUCCESS"></el-option>
                                <el-option label="失败" value="FAILED"></el-option>
                                <el-option label="运行中" value="RUNNING"></el-option>
                            </el-select>
                        </el-form-item>
                        <el-form-item>
                            <el-button type="primary" @click="queryLogs">查询</el-button>
                            <el-button @click="resetLogQuery">重置</el-button>
                            <el-button type="warning" @click="cleanOldLogs">清理30天前日志</el-button>
                        </el-form-item>
                    </el-form>
                    
                    <!-- 日志表格 -->
                    <el-table :data="logs" stripe border v-loading="logLoading">
                        <el-table-column prop="id" label="ID" width="80"></el-table-column>
                        <el-table-column prop="sourceTableName" label="源表名" width="180"></el-table-column>
                        <el-table-column prop="targetTableName" label="目标表名" width="180"></el-table-column>
                        <el-table-column label="同步类型" width="100">
                            <template #default="scope">
                                <el-tag :type="scope.row.syncType === 'FULL' ? 'warning' : 'primary'">
                                    {{ scope.row.syncType === 'FULL' ? '全量' : '增量' }}
                                </el-tag>
                            </template>
                        </el-table-column>
                        <el-table-column label="同步状态" width="100">
                            <template #default="scope">
                                <el-tag :type="scope.row.syncStatus === 'SUCCESS' ? 'success' : (scope.row.syncStatus === 'FAILED' ? 'danger' : 'info')">
                                    {{ scope.row.syncStatus === 'SUCCESS' ? '成功' : (scope.row.syncStatus === 'FAILED' ? '失败' : '运行中') }}
                                </el-tag>
                            </template>
                        </el-table-column>
                        <el-table-column prop="totalCount" label="总数" width="80"></el-table-column>
                        <el-table-column prop="successCount" label="成功数" width="90"></el-table-column>
                        <el-table-column prop="failCount" label="失败数" width="90"></el-table-column>
                        <el-table-column label="执行时长" width="100">
                            <template #default="scope">
                                {{ formatDuration(scope.row.duration) }}
                            </template>
                        </el-table-column>
                        <el-table-column prop="startTime" label="开始时间" width="180"></el-table-column>
                        <el-table-column prop="endTime" label="结束时间" width="180"></el-table-column>
                        <el-table-column label="操作" width="100" fixed="right">
                            <template #default="scope">
                                <el-button size="small" type="primary" @click="showLogDetail(scope.row)">详情</el-button>
                            </template>
                        </el-table-column>
                    </el-table>
                    
                    <!-- 分页 -->
                    <el-pagination
                        v-model:current-page="logQuery.pageNum"
                        v-model:page-size="logQuery.pageSize"
                        :page-sizes="[20, 50, 100, 200]"
                        layout="total, sizes, prev, pager, next, jumper"
                        :total="logTotal"
                        @size-change="queryLogs"
                        @current-change="queryLogs"
                        style="margin-top: 20px; justify-content: center;">
                    </el-pagination>
                </el-tab-pane>
                
                <!-- 线程池监控标签页 -->
                <el-tab-pane label="线程池监控" name="thread-pool">
                    <el-card shadow="hover">
                        <template #header>
                            <div style="display: flex; justify-content: space-between; align-items: center;">
                                <span>全量同步线程池状态</span>
                                <el-button type="primary" size="small" @click="refreshThreadPoolStatus">刷新状态</el-button>
                            </div>
                        </template>
                        
                        <el-row :gutter="20" v-if="threadPoolStatus">
                            <!-- 线程池基本信息 -->
                            <el-col :span="8">
                                <el-card shadow="never">
                                    <template #header>
                                        <span>线程池配置</span>
                                    </template>
                                    <el-descriptions :column="1" border>
                                        <el-descriptions-item label="核心线程数">
                                            <el-tag type="primary">{{ threadPoolStatus.corePoolSize }}</el-tag>
                                        </el-descriptions-item>
                                        <el-descriptions-item label="最大线程数">
                                            <el-tag type="warning">{{ threadPoolStatus.maxPoolSize }}</el-tag>
                                        </el-descriptions-item>
                                        <el-descriptions-item label="队列容量">
                                            <el-tag type="info">{{ threadPoolStatus.queueCapacity }}</el-tag>
                                        </el-descriptions-item>
                                        <el-descriptions-item label="历史最大线程数">
                                            {{ threadPoolStatus.largestPoolSize }}
                                        </el-descriptions-item>
                                    </el-descriptions>
                                </el-card>
                            </el-col>
                            
                            <!-- 运行状态 -->
                            <el-col :span="8">
                                <el-card shadow="never">
                                    <template #header>
                                        <span>运行状态</span>
                                    </template>
                                    <el-descriptions :column="1" border>
                                        <el-descriptions-item label="状态">
                                            <el-tag :type="getStatusType(threadPoolStatus.statusDesc)">
                                                {{ threadPoolStatus.statusDesc }}
                                            </el-tag>
                                        </el-descriptions-item>
                                        <el-descriptions-item label="活跃线程数">
                                            <el-tag type="success">{{ threadPoolStatus.activeThreads }}</el-tag>
                                        </el-descriptions-item>
                                        <el-descriptions-item label="当前线程数">
                                            {{ threadPoolStatus.poolSize }}
                                        </el-descriptions-item>
                                        <el-descriptions-item label="线程池利用率">
                                            <el-progress :percentage="threadPoolStatus.poolUtilization" 
                                                        :color="getUtilizationColor(threadPoolStatus.poolUtilization)" />
                                        </el-descriptions-item>
                                    </el-descriptions>
                                </el-card>
                            </el-col>
                            
                            <!-- 任务统计 -->
                            <el-col :span="8">
                                <el-card shadow="never">
                                    <template #header>
                                        <span>任务统计</span>
                                    </template>
                                    <el-descriptions :column="1" border>
                                        <el-descriptions-item label="总任务数">
                                            {{ threadPoolStatus.totalTasks }}
                                        </el-descriptions-item>
                                        <el-descriptions-item label="已完成任务">
                                            <el-tag type="success">{{ threadPoolStatus.completedTasks }}</el-tag>
                                        </el-descriptions-item>
                                        <el-descriptions-item label="队列中任务">
                                            <el-tag :type="threadPoolStatus.queueSize > 0 ? 'warning' : 'info'">
                                                {{ threadPoolStatus.queueSize }}
                                            </el-tag>
                                        </el-descriptions-item>
                                        <el-descriptions-item label="队列利用率">
                                            <el-progress :percentage="threadPoolStatus.queueUtilization"
                                                        :color="getUtilizationColor(threadPoolStatus.queueUtilization)" />
                                        </el-descriptions-item>
                                    </el-descriptions>
                                </el-card>
                            </el-col>
                        </el-row>
                        
                        <el-empty v-else description="暂无数据" />
                        
                        <!-- 配置说明 -->
                        <el-alert
                            title="线程池配置说明"
                            type="info"
                            :closable="false"
                            style="margin-top: 20px;">
                            <template #default>
                                <p>• <strong>核心线程数</strong>：线程池保持的最小线程数（建议：CPU核心数）</p>
                                <p>• <strong>最大线程数</strong>：线程池允许的最大线程数（建议：CPU核心数 × 2）</p>
                                <p>• <strong>队列容量</strong>：待执行任务队列的最大容量</p>
                                <p>• <strong>配置位置</strong>：application.yml 中的 sync.thread-pool 配置节</p>
                                <p style="margin-top: 10px; color: #67c23a;">
                                    💡 <strong>提示</strong>：如需调整线程池参数，请修改 application.yml 配置文件后重启应用
                                </p>
                            </template>
                        </el-alert>
                    </el-card>
                </el-tab-pane>
                
                <!-- 实时日志标签页 -->
                <el-tab-pane label="实时日志" name="realtime-logs">
                    <el-card shadow="hover">
                        <template #header>
                            <div style="display: flex; justify-content: space-between; align-items: center;">
                                <span>后端实时日志</span>
                                <div>
                                    <el-select v-model="logLevel" size="small" style="width: 120px; margin-right: 10px;" @change="reconnectLogStream">
                                        <el-option label="全部" value="ALL"></el-option>
                                        <el-option label="DEBUG" value="DEBUG"></el-option>
                                        <el-option label="INFO" value="INFO"></el-option>
                                        <el-option label="WARN" value="WARN"></el-option>
                                        <el-option label="ERROR" value="ERROR"></el-option>
                                    </el-select>
                                    <el-button type="primary" size="small" @click="toggleAutoScroll">
                                        {{ autoScroll ? '暂停滚动' : '继续滚动' }}
                                    </el-button>
                                    <el-button type="warning" size="small" @click="clearLogs">清空日志</el-button>
                                    <el-button type="success" size="small" @click="downloadLogs">下载日志</el-button>
                                    <el-tag :type="logConnected ? 'success' : 'danger'" style="margin-left: 10px;">
                                        {{ logConnected ? '已连接' : '未连接' }}
                                    </el-tag>
                                </div>
                            </div>
                        </template>
                        
                        <!-- 日志显示区域 -->
                        <div ref="logContainer" class="log-container">
                            <div v-if="realtimeLogs.length === 0" style="text-align: center; padding: 50px; color: #909399;">
                                暂无日志数据，等待实时日志推送...
                            </div>
                            <div v-for="(log, index) in realtimeLogs" :key="index" 
                                 :class="['log-line', 'log-' + log.level.toLowerCase()]">
                                <span class="log-timestamp">{{ log.timestamp }}</span>
                                <span class="log-thread">[{{ log.thread }}]</span>
                                <span class="log-level">{{ log.level }}</span>
                                <span class="log-logger">{{ log.logger }}</span>
                                <span class="log-message">{{ log.message }}</span>
                            </div>
                        </div>
                        
                        <!-- 日志统计 -->
                        <div style="margin-top: 10px; padding: 10px; background-color: #f5f7fa; border-radius: 4px;">
                            <el-row :gutter="20">
                                <el-col :span="6">
                                    <el-statistic title="日志总数" :value="realtimeLogs.length" />
                                </el-col>
                                <el-col :span="6">
                                    <el-statistic title="连接数" :value="subscriberCount" />
                                </el-col>
                                <el-col :span="6">
                                    <el-statistic title="日志级别" :value="logLevel" />
                                </el-col>
                                <el-col :span="6">
                                    <el-statistic title="自动滚动" :value="autoScroll ? '开启' : '关闭'" />
                                </el-col>
                            </el-row>
                        </div>
                    </el-card>
                </el-tab-pane>
            </el-tabs>
        </el-main>
    </el-container>
    
    <!-- 新增/编辑配置对话框 -->
    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="85%" :close-on-click-modal="false">
        <el-form :model="currentConfig" label-width="140px">
            <el-tabs v-model="activeTab">
                <el-tab-pane label="基本配置" name="basic">
                    <!-- 源表名 - 下拉选择 -->
                    <el-form-item label="源表名">
                        <el-select
                            v-model="currentConfig.sourceTableName"
                            filterable
                            placeholder="请选择 StarRocks 表"
                            @change="onSourceTableChange"
                            :loading="loadingSourceTables"
                            style="width: 100%;">
                            <el-option
                                v-for="table in sourceTableList"
                                :key="table.name"
                                :label="table.comment ? table.name + ' - ' + table.comment : table.name"
                                :value="table.name">
                                <span>{{ table.name }}</span>
                                <span v-if="table.comment" style="float: right; color: #909399; font-size: 12px;">{{ table.comment }}</span>
                            </el-option>
                        </el-select>
                    </el-form-item>
                    
                    <!-- 主键字段 - 下拉选择 -->
                    <el-form-item label="主键字段">
                        <el-select
                            v-model="currentConfig.primaryKey"
                            filterable
                            placeholder="请选择主键字段"
                            :disabled="!currentConfig.sourceTableName || sourceColumns.length === 0"
                            style="width: 100%;">
                            <el-option
                                v-for="col in sourceColumns"
                                :key="col.name"
                                :label="col.comment ? col.name + ' - ' + col.comment : col.name"
                                :value="col.name">
                                <div style="display: flex; justify-content: space-between;">
                                    <span>{{ col.name }}</span>
                                    <span style="color: #909399; font-size: 12px;">
                                        {{ col.type }}<span v-if="col.comment"> · {{ col.comment }}</span>
                                    </span>
                                </div>
                            </el-option>
                        </el-select>
                    </el-form-item>
                    
                    <!-- 更新时间字段 - 下拉选择 -->
                    <el-form-item label="更新时间字段">
                        <el-select
                            v-model="currentConfig.updateTimeField"
                            filterable
                            placeholder="请选择更新时间字段"
                            :disabled="!currentConfig.sourceTableName || sourceColumns.length === 0"
                            style="width: 100%;">
                            <el-option
                                v-for="col in sourceColumns"
                                :key="col.name"
                                :label="col.comment ? col.name + ' - ' + col.comment : col.name"
                                :value="col.name">
                                <div style="display: flex; justify-content: space-between;">
                                    <span>{{ col.name }}</span>
                                    <span style="color: #909399; font-size: 12px;">
                                        {{ col.type }}<span v-if="col.comment"> · {{ col.comment }}</span>
                                    </span>
                                </div>
                            </el-option>
                        </el-select>
                    </el-form-item>
                    
                    <!-- 源表软删除字段名配置 -->
                    <el-form-item label="软删除字段名">
                        <el-select
                            v-model="currentConfig.deleteField"
                            filterable
                            allow-create
                            clearable
                            placeholder="请选择或输入软删除字段名（不选则默认为 is_delete）"
                            :disabled="!currentConfig.sourceTableName || sourceColumns.length === 0"
                            style="width: 100%;">
                            <el-option
                                v-for="col in sourceColumns"
                                :key="col.name"
                                :label="col.comment ? col.name + ' - ' + col.comment : col.name"
                                :value="col.name">
                                <div style="display: flex; justify-content: space-between;">
                                    <span>{{ col.name }}</span>
                                    <span style="color: #909399; font-size: 12px;">
                                        {{ col.type }}<span v-if="col.comment"> · {{ col.comment }}</span>
                                    </span>
                                </div>
                            </el-option>
                        </el-select>
                        <div style="color: #999; font-size: 12px; margin-top: 5px;">
                            指定源表中的软删除字段名，用于查询时过滤软删除数据（1表示已删除，0或NULL表示未删除）<br>
                            <strong>不选择则默认使用 "is_delete"</strong>，如果源表使用其他字段名（如 is_deleted、deleted 等）请在此选择或输入<br>
                            目标表的软删除字段通过"字段映射"配置
                        </div>
                    </el-form-item>
                    
                    <el-form-item label="批量大小">
                        <el-input-number v-model="currentConfig.batchSize" :min="100" :max="50000"></el-input-number>
                    </el-form-item>
                    <el-form-item label="启动时全量同步">
                        <el-switch v-model="currentConfig.fullSyncOnStartup"></el-switch>
                    </el-form-item>
                    <el-form-item label="是否启用">
                        <el-switch v-model="currentConfig.enabled"></el-switch>
                    </el-form-item>
                    <el-form-item label="描述">
                        <el-input v-model="currentConfig.description" type="textarea" rows="2"></el-input>
                    </el-form-item>
                </el-tab-pane>
                
                <el-tab-pane label="目标表配置" name="targets">
                    <el-button type="primary" size="small" @click="addTarget" style="margin-bottom: 10px;">➕ 添加目标表</el-button>
                    
                    <el-collapse v-model="activeTargets">
                        <el-collapse-item v-for="(target, index) in currentConfig.targets" :key="index" :name="index">
                            <template #title>
                                <span style="margin-right: 10px; font-weight: bold;">
                                    目标表 {{ index + 1 }}: {{ target.targetTableName || '未设置' }}
                                </span>
                                <el-button type="danger" size="small" @click.stop="removeTarget(index)">删除</el-button>
                            </template>
                            
                            <!-- 目标表名 - 下拉选择 -->
                            <el-form-item label="目标表名">
                                <el-select
                                    v-model="target.targetTableName"
                                    filterable
                                    placeholder="请选择 TiDB 表"
                                    @change="onTargetTableChange(target)"
                                    :loading="target.loadingTargetColumns"
                                    style="width: 100%;">
                                    <el-option
                                        v-for="table in targetTableList"
                                        :key="table.name"
                                        :label="table.comment ? table.name + ' - ' + table.comment : table.name"
                                        :value="table.name">
                                        <span>{{ table.name }}</span>
                                        <span v-if="table.comment" style="float: right; color: #909399; font-size: 12px;">{{ table.comment }}</span>
                                    </el-option>
                                </el-select>
                            </el-form-item>
                            
                            <!-- 目标表主键 - 下拉选择 -->
                            <el-form-item label="目标表主键">
                                <el-select
                                    v-model="target.targetPrimaryKey"
                                    filterable
                                    clearable
                                    placeholder="留空则使用源表主键"
                                    :disabled="!target.targetTableName || !target.targetColumns || target.targetColumns.length === 0"
                                    style="width: 100%;">
                                    <el-option
                                        v-for="col in target.targetColumns"
                                        :key="col.name"
                                        :label="col.comment ? col.name + ' - ' + col.comment : col.name"
                                        :value="col.name">
                                        <div style="display: flex; justify-content: space-between;">
                                            <span>{{ col.name }}</span>
                                            <span style="color: #909399; font-size: 12px;">
                                                {{ col.type }}<span v-if="col.comment"> · {{ col.comment }}</span>
                                            </span>
                                        </div>
                                    </el-option>
                                </el-select>
                            </el-form-item>
                            
                            <!-- 使用目标表ID -->
                            <el-form-item label="使用目标表ID">
                                <el-switch v-model="target.useTargetId"></el-switch>
                                <div style="color: #999; font-size: 12px; margin-top: 5px;">
                                    开启后，目标表将使用自己的ID，而不是源表ID
                                </div>
                            </el-form-item>
                            
                            <!-- ID生成策略（仅当使用目标表ID时显示） -->
                            <el-form-item label="ID生成策略" v-if="target.useTargetId">
                                <el-select v-model="target.idGenerateStrategy" placeholder="请选择ID生成策略" style="width: 100%;">
                                    <el-option label="自动递增 (AUTO_INCREMENT)" value="AUTO_INCREMENT"></el-option>
                                    <el-option label="UUID" value="UUID"></el-option>
                                    <el-option label="雪花算法 (SNOWFLAKE)" value="SNOWFLAKE"></el-option>
                                </el-select>
                                <div style="color: #999; font-size: 12px; margin-top: 5px;">
                                    AUTO_INCREMENT: 数据库自增ID<br>
                                    UUID: 32位UUID字符串<br>
                                    SNOWFLAKE: 19位长整型分布式ID
                                </div>
                            </el-form-item>
                            
                            <!-- 软删除配置 -->
                            <el-form-item label="目标表软删除">
                                <el-switch v-model="target.useSoftDelete"></el-switch>
                                <div style="color: #999; font-size: 12px; margin-top: 5px;">
                                    关闭后，当源表数据被软删除时，目标表将执行物理删除<br>
                                    开启后，目标表保留删除标记，与源表保持一致<br>
                                    💡 源表的软删除字段名在"基本信息"标签页配置，目标表的软删除字段通过"字段映射"配置
                                </div>
                            </el-form-item>
                            
                            <!-- 同步字段 - 多选下拉 + 全选按钮 -->
                            <el-form-item label="同步字段">
                                <div style="display: flex; gap: 10px; align-items: flex-start;">
                                    <el-select
                                        v-model="target.syncFieldsList"
                                        multiple
                                        filterable
                                        placeholder="请选择要同步的字段（留空则同步所有字段）"
                                        :disabled="!currentConfig.sourceTableName || sourceColumns.length === 0"
                                        style="flex: 1;">
                                        <el-option
                                            v-for="col in sourceColumns"
                                            :key="col.name"
                                            :label="col.comment ? col.name + ' - ' + col.comment : col.name"
                                            :value="col.name">
                                            <div style="display: flex; justify-content: space-between;">
                                                <span>{{ col.name }}</span>
                                                <span style="color: #909399; font-size: 12px;">
                                                    {{ col.type }}<span v-if="col.comment"> · {{ col.comment }}</span>
                                                </span>
                                            </div>
                                        </el-option>
                                    </el-select>
                                    <el-button
                                        type="primary"
                                        :disabled="!currentConfig.sourceTableName || sourceColumns.length === 0"
                                        @click="selectAllFields(target)">
                                        全选
                                    </el-button>
                                    <el-button
                                        :disabled="!target.syncFieldsList || target.syncFieldsList.length === 0"
                                        @click="clearAllFields(target)">
                                        清空
                                    </el-button>
                                    <el-button
                                        type="success"
                                        @click="syncMappingToSyncFields(target)">
                                        从映射同步字段 ⬆️
                                    </el-button>
                                </div>
                                <div style="color: #999; font-size: 12px; margin-top: 5px;">
                                    留空则同步所有字段。点击"从映射同步字段"可将下方字段映射中的源字段一键添加到此处
                                </div>
                            </el-form-item>
                            
                            <!-- 字段映射 - 改进的表格 -->
                            <el-form-item label="字段映射">
                                <div>
                                    <el-button type="primary" size="small" @click="addFieldMapping(target)" style="margin-bottom: 10px;">
                                        + 添加映射
                                    </el-button>
                                    <el-table :data="target.fieldMappingList" border size="small" style="width: 100%;">
                                        <el-table-column label="源字段 (StarRocks)" width="350">
                                            <template #default="scope">
                                                <el-select
                                                    v-model="scope.row.source"
                                                    filterable
                                                    placeholder="请选择源字段"
                                                    :disabled="getAvailableSourceFields(target).length === 0"
                                                    style="width: 100%;">
                                                    <el-option
                                                        v-for="col in getAvailableSourceFields(target)"
                                                        :key="col.name"
                                                        :label="col.comment ? col.name + ' - ' + col.comment : col.name"
                                                        :value="col.name">
                                                        <div style="display: flex; justify-content: space-between; align-items: center;">
                                                            <span>{{ col.name }}</span>
                                                            <span style="color: #909399; font-size: 12px;">
                                                                {{ col.type }}<span v-if="col.comment"> · {{ col.comment }}</span>
                                                            </span>
                                                        </div>
                                                    </el-option>
                                                </el-select>
                                            </template>
                                        </el-table-column>
                                        <el-table-column label="目标字段 (TiDB)" width="350">
                                            <template #default="scope">
                                                <el-select
                                                    v-model="scope.row.target"
                                                    filterable
                                                    placeholder="请选择目标字段"
                                                    :disabled="!target.targetColumns || target.targetColumns.length === 0"
                                                    style="width: 100%;">
                                                    <el-option
                                                        v-for="col in target.targetColumns"
                                                        :key="col.name"
                                                        :label="col.comment ? col.name + ' - ' + col.comment : col.name"
                                                        :value="col.name"
                                                        :disabled="isTargetFieldUsed(target, col.name, scope.$index)">
                                                        <div style="display: flex; justify-content: space-between; align-items: center;">
                                                            <span :style="{color: isTargetFieldUsed(target, col.name, scope.$index) ? '#ccc' : ''}">{{ col.name }}</span>
                                                            <span style="color: #909399; font-size: 12px;">
                                                                {{ col.type }}<span v-if="col.comment"> · {{ col.comment }}</span>
                                                                <span v-if="isTargetFieldUsed(target, col.name, scope.$index)" style="color: #f56c6c;"> (已使用)</span>
                                                            </span>
                                                        </div>
                                                    </el-option>
                                                </el-select>
                                            </template>
                                        </el-table-column>
                                        <el-table-column label="操作" width="100">
                                            <template #default="scope">
                                                <el-button type="danger" size="small" @click="removeFieldMapping(target, scope.$index)">
                                                    删除
                                                </el-button>
                                            </template>
                                        </el-table-column>
                                    </el-table>
                                    <div style="color: #999; font-size: 12px; margin-top: 5px;">
                                        配置源字段到目标字段的映射关系，留空则字段名不变<br>
                                        <span style="color: #67c23a;">💡 提示：配置好映射关系后，可点击上方"从映射同步字段"按钮一键同步到同步字段</span>
                                    </div>
                                </div>
                            </el-form-item>
                            
                            <el-form-item label="同步优先级">
                                <el-input-number v-model="target.sortOrder" :min="0" :max="100"></el-input-number>
                                <span style="margin-left: 10px; color: #999; font-size: 12px;">数字越小越先执行</span>
                            </el-form-item>
                            
                            <el-form-item label="是否启用">
                                <el-switch v-model="target.enabled"></el-switch>
                            </el-form-item>
                        </el-collapse-item>
                    </el-collapse>
                </el-tab-pane>
            </el-tabs>
        </el-form>
        
        <template #footer>
            <el-button @click="dialogVisible = false">取消</el-button>
            <el-button type="primary" @click="saveConfig" :loading="saveLoading">保存</el-button>
        </template>
    </el-dialog>
    
    <!-- 日志详情对话框 -->
    <el-dialog v-model="logDetailVisible" title="同步日志详情" width="700px">
        <el-descriptions v-if="currentLog" :column="2" border>
            <el-descriptions-item label="日志ID">{{ currentLog.id }}</el-descriptions-item>
            <el-descriptions-item label="配置ID">{{ currentLog.configId || '无' }}</el-descriptions-item>
            <el-descriptions-item label="源表名">{{ currentLog.sourceTableName }}</el-descriptions-item>
            <el-descriptions-item label="目标表名">{{ currentLog.targetTableName }}</el-descriptions-item>
            <el-descriptions-item label="同步类型">
                <el-tag :type="currentLog.syncType === 'FULL' ? 'warning' : 'primary'">
                    {{ currentLog.syncType === 'FULL' ? '全量同步' : '增量同步' }}
                </el-tag>
            </el-descriptions-item>
            <el-descriptions-item label="同步状态">
                <el-tag :type="currentLog.syncStatus === 'SUCCESS' ? 'success' : (currentLog.syncStatus === 'FAILED' ? 'danger' : 'info')">
                    {{ currentLog.syncStatus === 'SUCCESS' ? '成功' : (currentLog.syncStatus === 'FAILED' ? '失败' : '运行中') }}
                </el-tag>
            </el-descriptions-item>
            <el-descriptions-item label="开始时间">{{ currentLog.startTime }}</el-descriptions-item>
            <el-descriptions-item label="结束时间">{{ currentLog.endTime || '未结束' }}</el-descriptions-item>
            <el-descriptions-item label="执行时长">{{ formatDuration(currentLog.duration) }}</el-descriptions-item>
            <el-descriptions-item label="总数据量">{{ currentLog.totalCount }}</el-descriptions-item>
            <el-descriptions-item label="成功数量">
                <el-tag type="success">{{ currentLog.successCount }}</el-tag>
            </el-descriptions-item>
            <el-descriptions-item label="失败数量">
                <el-tag :type="currentLog.failCount > 0 ? 'danger' : 'info'">{{ currentLog.failCount }}</el-tag>
            </el-descriptions-item>
            <el-descriptions-item label="最后同步时间点" :span="2">
                {{ currentLog.lastSyncTime || '无' }}
            </el-descriptions-item>
            <el-descriptions-item label="错误信息" :span="2" v-if="currentLog.errorMessage">
                <el-alert :title="currentLog.errorMessage" type="error" :closable="false"></el-alert>
            </el-descriptions-item>
            <el-descriptions-item label="失败源数据" :span="2" v-if="currentLog.sourceData && currentLog.syncStatus === 'FAILED'">
                <div style="max-height: 400px; overflow-y: auto; background: #f5f5f5; padding: 10px; border-radius: 4px;">
                    <pre style="margin: 0; white-space: pre-wrap; word-wrap: break-word;">{{ formatSourceData(currentLog.sourceData) }}</pre>
                </div>
                <div style="color: #999; font-size: 12px; margin-top: 5px;">
                    💡 显示前10条失败记录的源表数据，用于排查同步问题
                </div>
            </el-descriptions-item>
        </el-descriptions>
        <template #footer>
            <el-button type="primary" @click="logDetailVisible = false">关闭</el-button>
        </template>
    </el-dialog>
</div>

<!-- Vue 3 - 本地资源（内网部署） -->
<script src="/lib/js/vue.global.js"></script>
<!-- Element Plus - 本地资源（内网部署） -->
<script src="/lib/js/element-plus.js"></script>
<!-- Axios - 本地资源（内网部署） -->
<script src="/lib/js/axios.min.js"></script>

<script>
const { createApp, ref, reactive, onMounted, computed } = Vue;
const { ElMessage, ElMessageBox } = ElementPlus;

createApp({
    setup() {
        const configs = ref([]);
        const syncStatus = ref(null);
        const dialogVisible = ref(false);
        const dialogTitle = ref('新增配置');
        const activeTab = ref('basic');
        const activeMainTab = ref('config');
        const activeTargets = ref([0]);
        const syncLoading = ref(false);
        const reloadLoading = ref(false);
        const saveLoading = ref(false);
        
        // 日志相关
        const logs = ref([]);
        const logTotal = ref(0);
        const logLoading = ref(false);
        const logDetailVisible = ref(false);
        const currentLog = ref(null);
        const logQuery = ref({
            pageNum: 1,
            pageSize: 20,
            sourceTableName: '',
            targetTableName: '',
            syncType: '',
            syncStatus: ''
        });
        const logSourceTables = ref([]);  // 日志搜索用的源表列表
        const logTargetTables = ref([]);  // 日志搜索用的目标表列表
        
        // 线程池监控相关
        const threadPoolStatus = ref(null);
        
        // 实时日志相关
        const realtimeLogs = ref([]);
        const logLevel = ref('INFO');
        const autoScroll = ref(true);
        const logConnected = ref(false);
        const subscriberCount = ref(0);
        let eventSource = null;
        
        // 表和字段列表
        const sourceTableList = ref([]);
        const targetTableList = ref([]);
        const sourceColumns = ref([]);
        const loadingSourceTables = ref(false);
        const loadingTargetTables = ref(false);
        
        const currentConfig = ref({
            id: null,
            sourceTableName: '',
            primaryKey: '',
            updateTimeField: '',
            enabled: true,
            batchSize: 10000,
            fullSyncOnStartup: false,
            description: '',
            targets: []
        });
        
        // 加载源表列表
        const loadSourceTables = async () => {
            loadingSourceTables.value = true;
            try {
                const response = await axios.get('/api/meta/starrocks/tables');
                if (response.data.success) {
                    // 返回格式：[{name: "table1", comment: "备注1"}, ...]
                    sourceTableList.value = response.data.data || [];
                }
            } catch (error) {
                ElMessage.error('加载源表列表失败：' + error.message);
            } finally {
                loadingSourceTables.value = false;
            }
        };
        
        // 加载目标表列表
        const loadTargetTables = async () => {
            loadingTargetTables.value = true;
            try {
                const response = await axios.get('/api/meta/tidb/tables');
                if (response.data.success) {
                    // 返回格式：[{name: "table1", comment: "备注1"}, ...]
                    targetTableList.value = response.data.data || [];
                }
            } catch (error) {
                ElMessage.error('加载目标表列表失败：' + error.message);
            } finally {
                loadingTargetTables.value = false;
            }
        };
        
        // 加载源表字段
        const loadSourceColumns = async (tableName) => {
            try {
                const response = await axios.get(`/api/meta/starrocks/columns/${tableName}`);
                if (response.data.success) {
                    // 返回格式：[{name: "col1", type: "varchar", key: "PRI", comment: "备注", ...}, ...]
                    sourceColumns.value = response.data.data || [];
                }
            } catch (error) {
                ElMessage.error('加载源表字段失败：' + error.message);
            }
        };
        
        // 加载目标表字段
        const loadTargetColumns = async (target, tableName) => {
            target.loadingTargetColumns = true;
            try {
                const response = await axios.get(`/api/meta/tidb/columns/${tableName}`);
                if (response.data.success) {
                    // 返回格式：[{name: "col1", type: "varchar", key: "PRI", comment: "备注", ...}, ...]
                    target.targetColumns = response.data.data || [];
                }
            } catch (error) {
                ElMessage.error('加载目标表字段失败：' + error.message);
            } finally {
                target.loadingTargetColumns = false;
            }
        };
        
        // 源表改变时触发
        const onSourceTableChange = (tableName) => {
            if (tableName) {
                loadSourceColumns(tableName);
                // 清空主键和更新时间字段
                currentConfig.value.primaryKey = '';
                currentConfig.value.updateTimeField = '';
            }
        };
        
        // 目标表改变时触发
        const onTargetTableChange = (target) => {
            if (target.targetTableName) {
                loadTargetColumns(target, target.targetTableName);
                // 清空目标表主键
                target.targetPrimaryKey = '';
            }
        };
        
        // 加载配置列表
        const loadConfigs = async () => {
            try {
                const response = await axios.get('/api/config/list');
                console.log('🔵 loadConfigs 响应:', response.data);
                if (response.data.success) {
                    configs.value = response.data.data || [];
                    console.log('🔵 configs.value:', JSON.stringify(configs.value, null, 2));
                    // 配置加载后，更新日志搜索的表选项
                    loadLogTableOptions();
                }
            } catch (error) {
                ElMessage.error('加载配置失败：' + error.message);
            }
        };
        
        // 刷新同步状态
        const refreshStatus = async () => {
            try {
                const response = await axios.get('/api/sync/status');
                if (response.data.success) {
                    syncStatus.value = response.data.data;
                } else {
                    ElMessage.error('获取状态失败：' + response.data.message);
                }
            } catch (error) {
                ElMessage.error('获取同步状态失败：' + error.message);
            }
        };
        
        // 启动同步
        const startSync = async () => {
            syncLoading.value = true;
            try {
                const response = await axios.post('/api/sync/start');
                if (response.data.success) {
                    ElMessage.success('同步服务已启动');
                    await refreshStatus();
                } else {
                    ElMessage.error(response.data.message);
                }
            } catch (error) {
                ElMessage.error('启动同步失败：' + error.message);
            } finally {
                syncLoading.value = false;
            }
        };
        
        // 停止同步
        const stopSync = async () => {
            syncLoading.value = true;
            try {
                const response = await axios.post('/api/sync/stop');
                if (response.data.success) {
                    ElMessage.success('同步服务已停止');
                    await refreshStatus();
                } else {
                    ElMessage.error(response.data.message);
                }
            } catch (error) {
                ElMessage.error('停止同步失败：' + error.message);
            } finally {
                syncLoading.value = false;
            }
        };
        
        // 重新加载配置
        const reloadConfig = async () => {
            reloadLoading.value = true;
            try {
                const response = await axios.post('/api/sync/reload-config');
                if (response.data.success) {
                    ElMessage.success('配置已重新加载');
                    await refreshStatus();
                } else {
                    ElMessage.error(response.data.message);
                }
            } catch (error) {
                ElMessage.error('重新加载配置失败：' + error.message);
            } finally {
                reloadLoading.value = false;
            }
        };
        
        // 解析同步字段
        const parseSyncFields = (syncFieldsStr) => {
            if (!syncFieldsStr || !syncFieldsStr.trim()) {
                return [];
            }
            try {
                return JSON.parse(syncFieldsStr);
            } catch {
                return [];
            }
        };
        
        // 解析字段映射
        const parseFieldMapping = (fieldMappingStr) => {
            if (!fieldMappingStr || !fieldMappingStr.trim()) {
                return [];
            }
            try {
                const obj = JSON.parse(fieldMappingStr);
                return Object.keys(obj).map(key => ({
                    source: key,
                    target: obj[key]
                }));
            } catch {
                return [];
            }
        };
        
        // 显示新增对话框
        const showAddDialog = () => {
            dialogTitle.value = '新增同步配置';
            currentConfig.value = {
                id: null,
                sourceTableName: '',
                primaryKey: '',
                updateTimeField: '',
                deleteField: '',
                enabled: true,
                batchSize: 10000,
                fullSyncOnStartup: false,
                description: '',
                targets: []
            };
            sourceColumns.value = [];
            activeTab.value = 'basic';
            dialogVisible.value = true;
            
            // 加载表列表
            loadSourceTables();
            loadTargetTables();
        };
        
        // 显示编辑对话框
        const showEditDialog = (config) => {
            dialogTitle.value = '编辑同步配置';
            currentConfig.value = JSON.parse(JSON.stringify(config));
            
            console.log('🟢 编辑配置:', currentConfig.value);
            console.log('🟢 目标表数量:', currentConfig.value.targets ? currentConfig.value.targets.length : 0);
            
            // 处理目标表配置
            if (currentConfig.value.targets && currentConfig.value.targets.length > 0) {
                currentConfig.value.targets.forEach((target, index) => {
                    console.log(`🟢 处理目标表 ${index + 1}:`, target.targetTableName);
                    console.log(`  - 原始 syncFields:`, target.syncFields);
                    console.log(`  - 原始 fieldMapping:`, target.fieldMapping);
                    
                    // 解析同步字段列表
                    target.syncFieldsList = parseSyncFields(target.syncFields);
                    console.log(`  - 解析后 syncFieldsList:`, target.syncFieldsList);
                    
                    // 解析字段映射列表
                    target.fieldMappingList = parseFieldMapping(target.fieldMapping);
                    console.log(`  - 解析后 fieldMappingList:`, target.fieldMappingList);
                    
                    // 设置新字段的默认值
                    if (target.useTargetId === undefined || target.useTargetId === null) {
                        target.useTargetId = false;
                    }
                    if (!target.idGenerateStrategy) {
                        target.idGenerateStrategy = 'AUTO_INCREMENT';
                    }
                    if (target.useSoftDelete === undefined || target.useSoftDelete === null) {
                        target.useSoftDelete = false;
                    }
                    
                    // 初始化目标表字段
                    target.targetColumns = [];
                    target.loadingTargetColumns = false;
                    
                    // 加载目标表字段
                    if (target.targetTableName) {
                        console.log(`  加载目标表字段: ${target.targetTableName}`);
                        loadTargetColumns(target, target.targetTableName);
                    }
                });
                
                // 展开所有目标表配置
                activeTargets.value = currentConfig.value.targets.map((_, index) => index);
            } else {
                console.log('没有目标表配置');
                currentConfig.value.targets = [];
                activeTargets.value = [];
            }
            
            // 加载源表字段
            if (currentConfig.value.sourceTableName) {
                console.log('加载源表字段:', currentConfig.value.sourceTableName);
                loadSourceColumns(currentConfig.value.sourceTableName);
            }
            
            activeTab.value = 'basic';
            dialogVisible.value = true;
            
            // 加载表列表
            loadSourceTables();
            loadTargetTables();
        };
        
        // 添加目标表
        const addTarget = () => {
            currentConfig.value.targets.push({
                targetTableName: '',
                targetPrimaryKey: '',
                useTargetId: false,
                idGenerateStrategy: 'AUTO_INCREMENT',
                useSoftDelete: false,
                syncFieldsList: [],
                fieldMappingList: [],
                targetColumns: [],
                loadingTargetColumns: false,
                enabled: true,
                sortOrder: currentConfig.value.targets.length + 1
            });
            activeTargets.value = [currentConfig.value.targets.length - 1];
        };
        
        // 删除目标表
        const removeTarget = (index) => {
            currentConfig.value.targets.splice(index, 1);
        };
        
        // 添加字段映射
        const addFieldMapping = (target) => {
            target.fieldMappingList.push({
                source: '',
                target: ''
            });
        };
        
        // 删除字段映射
        const removeFieldMapping = (target, index) => {
            target.fieldMappingList.splice(index, 1);
        };
        
        // 全选同步字段
        const selectAllFields = (target) => {
            if (sourceColumns.value && sourceColumns.value.length > 0) {
                target.syncFieldsList = sourceColumns.value.map(col => col.name);
                ElMessage.success(`已选择所有 ${target.syncFieldsList.length} 个字段`);
            }
        };
        
        // 清空同步字段
        const clearAllFields = (target) => {
            target.syncFieldsList = [];
            ElMessage.info('已清空同步字段');
        };
        
        // 从字段映射同步到同步字段（将字段映射中的源字段添加到同步字段列表）
        const syncMappingToSyncFields = (target) => {
            if (!target.fieldMappingList || target.fieldMappingList.length === 0) {
                ElMessage.warning('请先配置字段映射');
                return;
            }
            
            // 获取字段映射中所有的源字段（过滤掉空值）
            const mappingSourceFields = target.fieldMappingList
                .map(m => m.source)
                .filter(field => field && field.trim() !== '');
            
            if (mappingSourceFields.length === 0) {
                ElMessage.warning('字段映射中没有配置源字段');
                return;
            }
            
            // 初始化同步字段列表（如果不存在）
            if (!target.syncFieldsList) {
                target.syncFieldsList = [];
            }
            
            // 获取当前已选择的同步字段
            const existingSyncFields = new Set(target.syncFieldsList);
            
            // 添加映射中的源字段到同步字段（去重）
            let addedCount = 0;
            mappingSourceFields.forEach(field => {
                if (!existingSyncFields.has(field)) {
                    target.syncFieldsList.push(field);
                    existingSyncFields.add(field);
                    addedCount++;
                }
            });
            
            if (addedCount > 0) {
                ElMessage.success(`已从字段映射中同步 ${addedCount} 个字段到同步字段列表`);
            } else {
                ElMessage.info('字段映射中的所有源字段都已在同步字段列表中');
            }
        };
        
        // 检查目标字段是否已被使用
        const isTargetFieldUsed = (target, fieldName, currentIndex) => {
            if (!target.fieldMappingList || target.fieldMappingList.length === 0) {
                return false;
            }
            
            // 检查除当前行外的其他行是否已使用该字段
            return target.fieldMappingList.some((mapping, index) => 
                index !== currentIndex && mapping.target === fieldName
            );
        };
        
        // 获取可用的源字段（返回所有源表字段）
        const getAvailableSourceFields = (target) => {
            // 返回所有源表字段，不再限制必须从同步字段中选择
            // 用户可以先配置字段映射，再点击"从映射同步字段"按钮同步到同步字段列表
            return sourceColumns.value;
        };
        
        // 保存配置
        const saveConfig = async () => {
            // 验证
            if (!currentConfig.value.sourceTableName) {
                ElMessage.warning('请选择源表名');
                activeTab.value = 'basic';
                return;
            }
            if (!currentConfig.value.primaryKey) {
                ElMessage.warning('请选择主键字段');
                activeTab.value = 'basic';
                return;
            }
            if (!currentConfig.value.updateTimeField) {
                ElMessage.warning('请选择更新时间字段');
                activeTab.value = 'basic';
                return;
            }
            
            // 转换数据格式
            const configToSave = JSON.parse(JSON.stringify(currentConfig.value));
            console.log('🔍 保存前 - currentConfig.value.targets:', JSON.stringify(currentConfig.value.targets, null, 2));
            
            if (configToSave.targets) {
                configToSave.targets.forEach((target, index) => {
                    console.log(`🔍 处理目标表 ${index + 1}:`, target.targetTableName);
                    console.log(`  - fieldMappingList:`, target.fieldMappingList);
                    
                    // 同步字段列表 → JSON数组字符串
                    if (target.syncFieldsList && target.syncFieldsList.length > 0) {
                        target.syncFields = JSON.stringify(target.syncFieldsList);
                        console.log(`  - syncFields: ${target.syncFields}`);
                    } else {
                        target.syncFields = null;
                    }
                    
                    // 字段映射列表 → JSON对象字符串
                    if (target.fieldMappingList && target.fieldMappingList.length > 0) {
                        const mappingObj = {};
                        target.fieldMappingList.forEach(item => {
                            console.log(`    - 映射项: ${item.source} -> ${item.target}`);
                            if (item.source && item.target) {
                                mappingObj[item.source] = item.target;
                            }
                        });
                        if (Object.keys(mappingObj).length > 0) {
                            target.fieldMapping = JSON.stringify(mappingObj);
                            console.log(`  - fieldMapping: ${target.fieldMapping}`);
                        } else {
                            target.fieldMapping = null;
                            console.log(`  - fieldMapping: null (映射对象为空)`);
                        }
                    } else {
                        target.fieldMapping = null;
                        console.log(`  - fieldMapping: null (fieldMappingList为空)`);
                    }
                    
                    // 删除辅助字段和缓存字段
                    delete target.syncFieldsList;
                    delete target.fieldMappingList;
                    delete target.targetColumns;
                    delete target.loadingTargetColumns;
                    delete target.fieldMappingMap;  // ⚠️ 删除旧的 Map，让后端根据 fieldMapping 重新生成
                });
            }
            
            console.log('🔍 保存时发送的数据:', JSON.stringify(configToSave, null, 2));
            
            saveLoading.value = true;
            try {
                const response = await axios.post('/api/config/save', configToSave);
                if (response.data.success) {
                    ElMessage.success('保存成功');
                    dialogVisible.value = false;
                    await loadConfigs();
                } else {
                    ElMessage.error(response.data.message);
                }
            } catch (error) {
                ElMessage.error('保存失败：' + error.message);
            } finally {
                saveLoading.value = false;
            }
        };
        
        // 切换配置启用状态
        const toggleConfig = async (config) => {
            try {
                const response = await axios.post(`/api/config/${config.id}/toggle`);
                if (response.data.success) {
                    ElMessage.success('状态已更新');
                } else {
                    ElMessage.error(response.data.message);
                    config.enabled = !config.enabled;
                }
            } catch (error) {
                ElMessage.error('更新状态失败：' + error.message);
                config.enabled = !config.enabled;
            }
        };
        
        // 删除配置
        const deleteConfig = async (id) => {
            try {
                await ElMessageBox.confirm('确定要删除这个配置吗？', '警告', {
                    confirmButtonText: '确定',
                    cancelButtonText: '取消',
                    type: 'warning'
                });
                
                const response = await axios.delete(`/api/config/${id}`);
                if (response.data.success) {
                    ElMessage.success('删除成功');
                    await loadConfigs();
                } else {
                    ElMessage.error(response.data.message);
                }
            } catch (error) {
                if (error !== 'cancel') {
                    ElMessage.error('删除失败：' + error.message);
                }
            }
        };
        
        // ========== 日志相关方法 ==========
        
        // 加载日志搜索的表选项
        const loadLogTableOptions = () => {
            const sourceTables = new Set();
            const targetTables = new Set();
            
            // 从已有配置中提取源表和目标表
            configs.value.forEach(config => {
                if (config.sourceTableName) {
                    sourceTables.add(config.sourceTableName);
                }
                if (config.targets && config.targets.length > 0) {
                    config.targets.forEach(target => {
                        if (target.targetTableName) {
                            targetTables.add(target.targetTableName);
                        }
                    });
                }
            });
            
            logSourceTables.value = Array.from(sourceTables).sort();
            logTargetTables.value = Array.from(targetTables).sort();
            
            console.log('日志搜索表选项已加载:', {
                sourceTables: logSourceTables.value,
                targetTables: logTargetTables.value
            });
        };
        
        // 查询日志
        const queryLogs = async () => {
            logLoading.value = true;
            try {
                const response = await axios.get('/api/log/list', {
                    params: {
                        pageNum: logQuery.value.pageNum,
                        pageSize: logQuery.value.pageSize,
                        sourceTableName: logQuery.value.sourceTableName,
                        targetTableName: logQuery.value.targetTableName,
                        syncType: logQuery.value.syncType,
                        syncStatus: logQuery.value.syncStatus
                    }
                });
                if (response.data.success) {
                    logs.value = response.data.data.records || [];
                    logTotal.value = response.data.data.total || 0;
                } else {
                    ElMessage.error(response.data.message);
                }
            } catch (error) {
                ElMessage.error('查询日志失败：' + error.message);
            } finally {
                logLoading.value = false;
            }
        };
        
        // 重置查询条件
        const resetLogQuery = () => {
            logQuery.value = {
                pageNum: 1,
                pageSize: 20,
                sourceTableName: '',
                targetTableName: '',
                syncType: '',
                syncStatus: ''
            };
            queryLogs();
        };
        
        // 显示日志详情
        const showLogDetail = (log) => {
            currentLog.value = log;
            logDetailVisible.value = true;
        };
        
        // 清理30天前的日志
        const cleanOldLogs = async () => {
            try {
                await ElMessageBox.confirm('确定要清理30天前的日志吗？', '警告', {
                    confirmButtonText: '确定',
                    cancelButtonText: '取消',
                    type: 'warning'
                });
                
                const response = await axios.delete('/api/log/clean', {
                    params: { days: 30 }
                });
                if (response.data.success) {
                    ElMessage.success(response.data.data);
                    await queryLogs();
                } else {
                    ElMessage.error(response.data.message);
                }
            } catch (error) {
                if (error !== 'cancel') {
                    ElMessage.error('清理日志失败：' + error.message);
                }
            }
        };
        
        // 格式化执行时长
        const formatDuration = (duration) => {
            if (!duration) return '-';
            if (duration < 1000) return duration + 'ms';
            if (duration < 60000) return (duration / 1000).toFixed(2) + 's';
            return (duration / 60000).toFixed(2) + 'min';
        };
        
        // 格式化源数据（JSON 格式化）
        const formatSourceData = (sourceData) => {
            if (!sourceData) return '无';
            try {
                const data = typeof sourceData === 'string' ? JSON.parse(sourceData) : sourceData;
                return JSON.stringify(data, null, 2);
            } catch (e) {
                return sourceData;
            }
        };
        
        // ==== 线程池监控相关方法 ====
        
        // 刷新线程池状态
        const refreshThreadPoolStatus = async () => {
            try {
                const response = await axios.get('/api/thread-pool/status');
                if (response.data.success) {
                    threadPoolStatus.value = response.data.data;
                } else {
                    ElMessage.error(response.data.message);
                }
            } catch (error) {
                ElMessage.error('获取线程池状态失败：' + error.message);
            }
        };
        
        // 获取状态类型
        const getStatusType = (statusDesc) => {
            switch(statusDesc) {
                case '空闲': return 'info';
                case '正常': return 'success';
                case '繁忙': return 'warning';
                case '满载': return 'danger';
                default: return 'info';
            }
        };
        
        // 获取利用率颜色
        const getUtilizationColor = (percentage) => {
            if (percentage < 50) return '#67c23a';  // 绿色
            if (percentage < 80) return '#e6a23c';  // 橙色
            return '#f56c6c';  // 红色
        };
        
        // ==================== 实时日志方法 ====================
        
        // 连接实时日志流
        const connectLogStream = () => {
            if (eventSource) {
                eventSource.close();
            }
            
            const url = `/api/logs/stream?level=${logLevel.value}`;
            eventSource = new EventSource(url);
            
            eventSource.addEventListener('connected', (e) => {
                console.log('日志流连接成功:', e.data);
                logConnected.value = true;
                const data = JSON.parse(e.data);
                subscriberCount.value = data.subscriberCount;
                ElMessage.success('实时日志连接成功');
            });
            
            eventSource.addEventListener('log', (e) => {
                const log = JSON.parse(e.data);
                realtimeLogs.value.push(log);
                
                // 限制日志条数（最多保留5000条）
                if (realtimeLogs.value.length > 5000) {
                    realtimeLogs.value.shift();
                }
                
                // 自动滚动到底部
                if (autoScroll.value) {
                    Vue.nextTick(() => {
                        const container = document.querySelector('.log-container');
                        if (container) {
                            container.scrollTop = container.scrollHeight;
                        }
                    });
                }
            });
            
            eventSource.onerror = (e) => {
                console.error('日志流连接错误:', e);
                logConnected.value = false;
                ElMessage.error('日志流连接断开，尝试重新连接...');
                
                // 5秒后重连
                setTimeout(() => {
                    if (activeMainTab.value === 'realtime-logs') {
                        connectLogStream();
                    }
                }, 5000);
            };
        };
        
        // 断开日志流
        const disconnectLogStream = () => {
            if (eventSource) {
                eventSource.close();
                eventSource = null;
                logConnected.value = false;
            }
        };
        
        // 重新连接日志流（切换日志级别时）
        const reconnectLogStream = () => {
            disconnectLogStream();
            realtimeLogs.value = [];  // 清空现有日志
            connectLogStream();
        };
        
        // 加载历史日志
        const loadHistoryLogs = async () => {
            try {
                const response = await axios.get('/api/logs/history');
                if (response.data) {
                    realtimeLogs.value = response.data;
                    // 滚动到底部
                    Vue.nextTick(() => {
                        const container = document.querySelector('.log-container');
                        if (container) {
                            container.scrollTop = container.scrollHeight;
                        }
                    });
                }
            } catch (error) {
                console.error('加载历史日志失败:', error);
            }
        };
        
        // 切换自动滚动
        const toggleAutoScroll = () => {
            autoScroll.value = !autoScroll.value;
            if (autoScroll.value) {
                Vue.nextTick(() => {
                    const container = document.querySelector('.log-container');
                    if (container) {
                        container.scrollTop = container.scrollHeight;
                    }
                });
            }
        };
        
        // 清空日志
        const clearLogs = () => {
            realtimeLogs.value = [];
            ElMessage.success('日志已清空');
        };
        
        // 下载日志
        const downloadLogs = () => {
            if (realtimeLogs.value.length === 0) {
                ElMessage.warning('暂无日志可下载');
                return;
            }
            
            // 生成文本内容
            const content = realtimeLogs.value.map(log => 
                `${log.timestamp} [${log.thread}] ${log.level} ${log.logger} - ${log.message}`
            ).join('\n');
            
            // 创建下载
            const blob = new Blob([content], { type: 'text/plain;charset=utf-8' });
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = `logs-${new Date().toISOString().replace(/[:.]/g, '-')}.txt`;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            URL.revokeObjectURL(url);
            
            ElMessage.success('日志已下载');
        };
        
        // 获取订阅者数量
        const refreshSubscriberCount = async () => {
            try {
                const response = await axios.get('/api/logs/subscriber-count');
                subscriberCount.value = response.data;
            } catch (error) {
                console.error('获取订阅者数量失败:', error);
            }
        };
        
        // 监听标签页切换
        const { watch } = Vue;
        watch(activeMainTab, (newVal, oldVal) => {
            if (newVal === 'log') {
                loadLogTableOptions();  // 先加载表选项
                queryLogs();            // 再查询日志
            } else if (newVal === 'thread-pool') {
                refreshThreadPoolStatus();  // 刷新线程池状态
            } else if (newVal === 'realtime-logs') {
                // 进入实时日志页面
                loadHistoryLogs();  // 先加载历史日志
                connectLogStream();  // 再连接实时流
                refreshSubscriberCount();  // 刷新订阅者数量
            }
            
            // 离开实时日志页面时断开连接
            if (oldVal === 'realtime-logs' && newVal !== 'realtime-logs') {
                disconnectLogStream();
            }
        });
        
        onMounted(() => {
            loadConfigs();
            refreshStatus();
        });
        
        return {
            configs,
            syncStatus,
            dialogVisible,
            dialogTitle,
            activeTab,
            activeMainTab,
            activeTargets,
            currentConfig,
            syncLoading,
            reloadLoading,
            saveLoading,
            sourceTableList,
            targetTableList,
            sourceColumns,
            loadingSourceTables,
            loadingTargetTables,
            // 日志相关
            logs,
            logTotal,
            logLoading,
            logDetailVisible,
            currentLog,
            logQuery,
            logSourceTables,
            logTargetTables,
            // 线程池相关
            threadPoolStatus,
            // 方法
            loadConfigs,
            refreshStatus,
            startSync,
            stopSync,
            reloadConfig,
            showAddDialog,
            showEditDialog,
            addTarget,
            removeTarget,
            addFieldMapping,
            removeFieldMapping,
            selectAllFields,
            clearAllFields,
            syncMappingToSyncFields,
            isTargetFieldUsed,
            getAvailableSourceFields,
            saveConfig,
            toggleConfig,
            deleteConfig,
            onSourceTableChange,
            onTargetTableChange,
            // 日志方法
            loadLogTableOptions,
            queryLogs,
            resetLogQuery,
            showLogDetail,
            cleanOldLogs,
            formatDuration,
            formatSourceData,
            // 线程池方法
            refreshThreadPoolStatus,
            getStatusType,
            getUtilizationColor,
            // 实时日志状态
            realtimeLogs,
            logLevel,
            autoScroll,
            logConnected,
            subscriberCount,
            // 实时日志方法
            connectLogStream,
            disconnectLogStream,
            reconnectLogStream,
            loadHistoryLogs,
            toggleAutoScroll,
            clearLogs,
            downloadLogs,
            refreshSubscriberCount
        };
    }
}).use(ElementPlus).mount('#app');
</script>
</body>
</html>
